﻿namespace Spectral.Operations.Rewriter
{
    using System;
    using System.Linq;
    using Mono.Cecil;
    using Mono.Cecil.Cil;

    public static class Program
    {
        public static void Main(string[] args)
        {
            var target = args[0];

            var assembly = AssemblyDefinition.ReadAssembly(target);
            var module = assembly.MainModule;
            var type = module.Types.First(n => n.Name == "Operator`1");

            var methods = new[]
            {
                new { Name = "get_Zero", Instruction = Instruction.Create(OpCodes.Ldc_I4_0) },
                new { Name = "get_One", Instruction = Instruction.Create(OpCodes.Ldc_I4_1) },
                new { Name = "Add", Instruction = Instruction.Create(OpCodes.Add) },
                new { Name = "Subtract", Instruction = Instruction.Create(OpCodes.Sub) },
                new { Name = "Multiply", Instruction = Instruction.Create(OpCodes.Mul) },
                new { Name = "Divide", Instruction = Instruction.Create(OpCodes.Div) },
                new { Name = "Remainder", Instruction = Instruction.Create(OpCodes.Rem) },
                new { Name = "Negate", Instruction = Instruction.Create(OpCodes.Neg) },
                new { Name = "Sqrt", Instruction = Instruction.Create(OpCodes.Call, module.Import(typeof(Math).GetMethod("Sqrt"))) },
                new { Name = "Pow", Instruction = Instruction.Create(OpCodes.Call, module.Import(typeof(Math).GetMethod("Pow"))) },
                new { Name = "Floor", Instruction = Instruction.Create(OpCodes.Call, module.Import(typeof(Math).GetMethod("Floor", new[] { typeof(double) }))) },
                new { Name = "Ceiling", Instruction = Instruction.Create(OpCodes.Call, module.Import(typeof(Math).GetMethod("Ceiling", new[] { typeof(double) }))) },
                new { Name = "Sin", Instruction = Instruction.Create(OpCodes.Call, module.Import(typeof(Math).GetMethod("Sin"))) },
                new { Name = "Cos", Instruction = Instruction.Create(OpCodes.Call, module.Import(typeof(Math).GetMethod("Cos"))) },
            };

            foreach (var method in methods)
                type.RewriteMethod(method.Name, method.Instruction);

            assembly.Write(target);
        }

        private static void RewriteMethod(this TypeDefinition type, string methodName, Instruction instruction)
        {
            var method = type.Methods.First(n => n.Name == methodName);
            var body = method.Body;
            var processor = body.GetILProcessor();

            body.Instructions.Clear();
            body.Variables.Clear();

            for (int i = 0; i < method.Parameters.Count; i++)
            {
                var opCodeName = "Ldarg_" + i.ToString();
                var field = typeof(OpCodes).GetField(opCodeName);
                if (field == null)
                    processor.Emit(OpCodes.Ldarg, i);
                else
                    processor.Emit((OpCode)field.GetValue(null));
            }

            processor.Append(instruction);
            processor.Emit(OpCodes.Ret);

            Console.WriteLine("Rewrote method: {0}", methodName);
        }
    }
}
